using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using CameraViewer;
using Projectiles;

namespace MGHGame
{
    /// <summary>
    /// Main game Driver
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        // constant definitions
        // the boundary is the size of the world
        // the float value represents half of the world, so;
        // BOUNDARY = 16.0f would really mean the world is 32 units by 32 units
        private const float BOUNDARY = 16.0f;

        // accesses drawing methods and properties
        GraphicsDeviceManager graphics;

        // handle mouse on the PC
#if !XBOX
        MouseState mouse;
#endif

        // for loading and drawing 2D images on the game window
        SpriteBatch spriteBatch;

        // load and access PositionColor.fx shader
        private Effect positionColorEffect;    // shader object
        private EffectParameter positionColorEffectWVP; // to set display matrix for window

        // load and access Texture.fx shader
        private Effect textureEffect;          // shader object                 
        private EffectParameter textureEffectWVP;       // cumulative matrix w*v*p 
        private EffectParameter textureEffectImage;     // texture parameter

        // camera 
        private Camera cam = new Camera();

        // vertex types and buffers
        private VertexDeclaration positionColor;
        private VertexDeclaration positionColorTexture;

        private IndexBuffer indexBuffer;    // reference vertices
        private VertexBuffer vertexBuffer;   // vertex storage

        const int NUM_COLS = 20;
        const int NUM_ROWS = 20;

        private VertexDeclaration positionNormalTexture;

        BasicEffect basicEffect;

        // pixel-shader-lighting shader objects
        private Effect lightEffect;            // point light shader
        private EffectParameter lightEffectWorld;       // world matrix
        private EffectParameter lightEffectWVP;         // wvp matrix
        private EffectParameter lightEffectPosition;    // light position
        private EffectParameter lightEffectIntensity;   // point light strength
        private EffectParameter lightEffectTexture;     // texture
        private EffectParameter lightEffectColor;       // color of point light

        // object for using font handling methods
        private SpriteFont spriteFont;

        // bounding boxes for the walls, used for collision detection
        BoundingBox[] wallBoxes = new BoundingBox[4];

        // an array to house the walls of the loaded model,
        // also used for collision detection
        WallArray wallArray = new WallArray();

        // represents the distance that the camera is from the ghost
        Vector3 distanceFromGhost;

        //create an array of shells
        const int NUM_SHELLS = 5;
        private Projectile[] shell = new Projectile[NUM_SHELLS];

        // declare objects for loading shotgun shell model
        Model shellModel; Matrix[] shellMatrix;
        const float BASE_HEIGHT = 0.6f; // start height for model

        // input device tracking for shotgun
#if !XBOX
        MouseState mouseCurrent, mousePrevious;
#endif
        GamePadState gamepad, gamepadPrevious;

        // ground vertices and texture
        VertexPositionColorTexture[]
            groundVertices = new VertexPositionColorTexture[4];
        private Texture2D grassTexture;
		
		// model for the maze that is loaded in
		Model mazeModel;
		// matrix for the maze that is loaded in
        Matrix[] mazeMatrix;

        // model for the shotgun
        Model gunModel;
        // matrix for calculations involving the shotgun
        Matrix[] gunMatrix;

		// handling multiple models
        const int MODEL_NUM = 0;
        
        // has the gun been picked up yet?
        bool GUN_ACTIVE = false; // false means that the gun has NOT been picked up

        // for the rotation of the shotgun
        private float gunRotation = 0.0f;

        // textures for the skybox: front, back, left, right, sky, and the new ground texture
        Texture2D frontTexture, backTexture, leftTexture, rightTexture, skyTexture, groundTexture;

        // texture for the walls
        Texture2D wallTexture;

        // triangleStrip stores vertices for the walls of the map
        private VertexPositionColorTexture[] wallVertices = new VertexPositionColorTexture[16];

        // vertex coordinates for each rectangle in the skybox
        private VertexPositionColorTexture[] skyVertices = new VertexPositionColorTexture[4];

        // "EDGE" is twice the distance of the boundary of the map,
        // it is used to state how far the skybox starts from the player
        private const float EDGE = BOUNDARY * 2.0f;

        //////////////////////////////////////////////////////////////////////////////////////////////////
        // vertices for the ghost (CURRENT IMPLEMENTATION OF THE GHOST IS A BILLBOARDED TRIANGLE STRIP)
        //                        (THIS WILL BE CHANGED IN A FUTURE RELEASE)
        //                        (the purpose of using a primitive is mainly for having a simplistic
        //                         tool to test AI while it is in development)
        private VertexPositionColorTexture[] ghostPrimitive = new VertexPositionColorTexture[4];

        // the actual ghost object is of type Enemy, giving it the ability to encorporate AI
        Enemy ghost = new Enemy();

        // the texture for the ghost
        Texture2D ghostTexture;

        // this is where the ghost is going to spawn
        Vector3 ghostPosition = new Vector3(7.0f, 0.0f, -5.0f);

        // for the random number seed
        Vector3 speed;

        // gameTime clone, used for incrementation, rather than relying on updates from gameTime
        int gameTimeSupp = 0;
		
		// these are in the calculation of the enemy movement
        int currentTime; int lastPass = 0; int descision = 1;
        //////////////////////////////////////////////////////////////////////////////////////////////////

        // on/off toggles
        // not specific to any one algorithm, etc.
        // ie. can be used in mutliple scenarios
        const bool ON = true;
        const bool OFF = false;

        // random number generator
        Random randomNumber = new Random();

        // is the enemy moving in the X or Z direction?
        int X_OR_Z;

        // initializes moving the enemy in a random starting direction
        int GHOST_START = 0;

        // this is where the gun spawns in the maze
        // also used for bounding sphere
        //Vector3 GUN_GROUND_POSITION = new Vector3(5.0f, 0.5f, -2.0f);

        //////////////////////////////////////////////////////////////////////////////////////////////////
        // implementing collision spheres
        // TODO:
        //       - implement collision sphere for the player
        //       - implement collision detection between player and shotgun
        //       - possibly revamp the collision detection between the player and the walls

        // enumerated list for sphere groups, helps to keep track of collision spheres
        public enum Group
        {
            player, pinky, shotgun, shell
        }

        // the total amount of sphere groups
        const int TOTAL_GROUPS = 3;

        // create the appropriate amount of sphere groups, for each object
        Sphere[] sphereGroup = new Sphere[TOTAL_GROUPS];

        // this dictates whether or not the spheres will be shown
        // only used for debugging purposes, make sure the final version is set to FALSE
        const bool SHOW = true;
        //////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Initializes:    -GraphicsDeviceManager object for drawing 
        ///                 -ContentManager object for loading media
        /// </summary>
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        public void InitializeWallCollision()
        {
            wallBoxes[0] = new BoundingBox(new Vector3(10.05f, 0.0f, -8.0f),    //right wall
                                           new Vector3(10.29f, 5.0f,  1.62f));
            wallBoxes[1] = new BoundingBox(new Vector3(-4.43f, 0.0f, -8.0f),    //left wall
                                           new Vector3(-4.35f, 5.0f, 1.62f));
            wallBoxes[2] = new BoundingBox(new Vector3(-4.43f, 0.0f, 1.49f),    //back wall
                                           new Vector3(10.29f, 5.0f, 1.73f));
            wallBoxes[3] = new BoundingBox(new Vector3(-4.43f, 0.0f, -8.20f),   //front wall
                                           new Vector3(10.29f, 5.0f, -7.94f));

            wallArray.InitializeWalls();
        }

        private void InitializeBasicEffect()
        {
            basicEffect = new BasicEffect(graphics.GraphicsDevice, null);
            basicEffect.TextureEnabled = true;             // needed if objects are textured
            basicEffect.LightingEnabled = true;             // must be on for lighting effect
            basicEffect.SpecularPower = 5.0f;             // highlights
            basicEffect.AmbientLightColor
                = new Vector3(0.6f, 0.6f, 0.5f);            // background light
            basicEffect.DirectionalLight0.Enabled = true;   // turn on light
            basicEffect.DirectionalLight0.DiffuseColor      // diffuse color
                = new Vector3(0.2f, 0.2f, 0.2f);            // rgb range 0 to 1
            basicEffect.DirectionalLight0.SpecularColor     // highlight color
                = new Vector3(0.5f, 0.5f, 0.37f);           // rgb range 0 to 1
            basicEffect.DirectionalLight0.Direction         // set normalized
                = Vector3.Normalize(new Vector3(0.0f, -1.0f, -1.0f));// direction
        }

        private void InitializeIndices()
        {
            short[] indices;                                             // indices for 1 subset
            indices = new short[2 * NUM_COLS];                   // sized for 1 subset
            indexBuffer = new IndexBuffer(
                                                graphics.GraphicsDevice, // graphics device
                                                typeof(short),           // data type is short
                                                indices.Length,          // array size in bytes
                                                BufferUsage.WriteOnly);  // memory allocation

            // store indices for one subset of vertices
            // see Figure 11-2 for the first subset of indices
            int counter = 0;
            for (int col = 0; col < NUM_COLS; col++)
            {
                indices[counter++] = (short)col;
                indices[counter++] = (short)(col + NUM_COLS);
            }
            indexBuffer.SetData(indices); // store in index buffer
        }

        private void LightingShader(PrimitiveType primitiveType)
        {
            // avoid drawing back face for large amounts of vertices
            graphics.GraphicsDevice.RenderState.CullMode =
                     CullMode.CullClockwiseFace;
            lightEffect.Begin();
            lightEffect.Techniques[0].Passes[0].Begin();

            // 5: draw object - select vertex type, primitive type, index, & draw
            graphics.GraphicsDevice.VertexDeclaration = positionNormalTexture;
            graphics.GraphicsDevice.Indices = indexBuffer;
            graphics.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0,
                                                          VertexPositionNormalTexture.SizeInBytes);
            // draw grid one row at a time
            for (int Z = 0; Z < NUM_ROWS - 1; Z++)
            {
                graphics.GraphicsDevice.DrawIndexedPrimitives(
                                        primitiveType,       // primitive
                                        Z * NUM_COLS,        // start point in vertex
                                        0,                   // vertex buffer offset
                                        NUM_COLS * NUM_ROWS, // total verts in vertex buffer
                                        0,                   // start point in index buffer
                                        2 * (NUM_COLS - 1)); // end point in index buffer
            }

            // end shader
            lightEffect.Techniques[0].Passes[0].End();
            lightEffect.End();

            // disable back face culling
            graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
        }

        private void InitializeVertexBuffer()
        {
            vertexBuffer = new VertexBuffer(
                                graphics.GraphicsDevice,            // graphics device
                                typeof(VertexPositionNormalTexture),// vertex type
                                NUM_COLS * NUM_ROWS,                // element count
                                BufferUsage.WriteOnly);             // memory use

            // store vertices temporarily while initializing them
            VertexPositionNormalTexture[] vertex
                = new VertexPositionNormalTexture[NUM_ROWS * NUM_COLS];

            // set grid width and height
            float colWidth = (float)2 * BOUNDARY / (NUM_COLS - 1);
            float rowHeight = (float)2 * BOUNDARY / (NUM_ROWS - 1);

            // set position, color, and texture coordinates
            for (int row = 0; row < NUM_ROWS; row++)
            {
                for (int col = 0; col < NUM_COLS; col++)
                {
                    // set X, Y, Z
                    float X = -BOUNDARY + col * colWidth;
                    float Y = 0.0f;
                    float Z = -BOUNDARY + row * rowHeight;
                    vertex[col + row * NUM_COLS].Position = new Vector3(X, Y, Z);

                    // set normal
                    vertex[col + row * NUM_COLS].Normal = new Vector3(0.0f, 1.0f, 0.0f);

                    // set UV coordinates to map texture 1:1
                    float U = (float)col / (float)(NUM_COLS - 1);
                    float V = (float)row / (float)(NUM_ROWS - 1);
                    vertex[col + row * NUM_COLS].TextureCoordinate
                            = new Vector2(U, V);
                }
            }
            // commit data to vertex buffer
            vertexBuffer.SetData(vertex);
        }

        /// <summary>
        /// PlayerSphere() uses sphereGroup to create (a) bounding sphere(s)
        /// used for collision detection. This method is unique to creating a bounding
        /// sphere used by the player (camera). Seperate helper functions will be
        /// used for walls, enemies, pickups, etc.
        /// </summary>
        /// <param name="group">The sphere group being initialized.</param>
        /// <param name="radius">Radius of the sphere</param>
        void PlayerSphere(Group group, float radius)
        {
            // color of the sphere to be drawn, required in the shader,
            // but is mostly just for cosmetic purposes, or to call your colleagues
            // over to laugh at the fact that you just created big blue balls.
            Color color = Color.Pink;

            // the position that the sphere is going to be drawn at
            // the player's sphere should obviously be on top of the player (camera)
            Vector3 position = cam.position;

            // adds the new information of the sphere to the group previously passed in
            sphereGroup[(int)group].AddSphere(radius, position, color);
        }

        /// <summary>
        /// GhostSphere() uses sphereGroup to create (a) bounding sphere(s)
        /// used for collision detection. This method is unique to creating a bounding
        /// sphere used by the ghost.
        /// </summary>
        /// <param name="group">The sphere group being initialized.</param>
        /// <param name="radius">Radius of the sphere</param>
        void GhostSphere(Group group, float radius)
        {
            // color of the sphere to be drawn
            Color color = Color.Teal;

            // the position that the sphere is going to be drawn at
            //Vector3 position = ghostPosition;

            // adds the new information of the sphere to the group previously passed in
            sphereGroup[(int)group].AddSphere(radius, ghostPosition, color);
        }

        /// <summary>
        /// Same deal here, just this is for the shotgun.
        /// </summary>
        /// <param name="group">Group.</param>
        /// <param name="radius">Radius.</param>
        void GunSphere(Group group, float radius)
        {
            // color of the sphere to be drawn
            Color color = Color.Yellow;

            // the position that the sphere is going to be drawn at
            Vector3 position = new Vector3(5.0f, 0.5f, -4.0f);

            // adds the new information of the sphere to the group previously passed in
            sphereGroup[(int)group].AddSphere(radius, position, color);
        }

        void ShellSphere(Group group, float radius)
        {
            // color of the sphere to be drawn
            Color color = Color.Green;

            // the position that the sphere is going to be drawn at
            

            // adds the new information of the sphere to the group previously passed in
            for (int i = 0; i < NUM_SHELLS; i++)
            {
                Vector3 position = shell[i].position;
                sphereGroup[(int)group].AddSphere(radius, position, color);
            }
        }

        /// <summary>
        /// Initializes the collision sphere used by the player.
        /// </summary>
        void InitializePlayerSphere()
        {
            // initializes the sphere for the player (camera)
            // SHOW determines wheter or not the sphere is drawn (see declaration)
            sphereGroup[(int)Group.player] = new Sphere(SHOW);

            // radius for the collision sphere
            const float RADIUS = 0.5f;

            // here is the initialization of the sphere
            PlayerSphere(Group.player, RADIUS);
        }

        /// <summary>
        /// Collision sphere for the gun.
        /// </summary>
        void InitializeGunSphere()
        {
            // allocate space for the collision sphere
            sphereGroup[(int)Group.shotgun] = new Sphere(SHOW);

            // the radius for the bounding sphere
            const float radius = 0.3f;

            // this is where the "actual" initialization takes place
            GunSphere(Group.shotgun, radius);
        }

        /// <summary>
        /// Initializees the collision sphere for the ghost
        /// </summary>
        void InitializeGhostSphere()
        {
            // allocate space for the collision sphere
            sphereGroup[(int)Group.pinky] = new Sphere(SHOW);

            // the radius for the bounding sphere
            const float radius = 1.0f;

            // this is where the "actual" initialization takes place
            GhostSphere(Group.pinky, radius);
        }

        private void DrawIndexedGrid(string surfaceName)
        {
            // 1: declare matrices
            Matrix world, translate, rotateX, scale, rotateY;

            // 2: initialize matrices
            translate = Matrix.CreateTranslation(0.0f, 0.0f, 0.0f);
            scale = Matrix.CreateScale(0.8f, 0.8f, 0.8f);
            rotateY = Matrix.CreateRotationY(0.0f);
            rotateX = Matrix.CreateRotationX(0.0f);

            /*if (surfaceName == "wall")
            {
                // set parameters for wall
                rotateX = Matrix.CreateRotationX(MathHelper.Pi / 2.0f);
                translate = Matrix.CreateTranslation(0.0f, 9.20f, -12.8f);
                lightEffectTexture.SetValue(wallTexture);
            }
            else*/ if (surfaceName == "ground") // set parameters for ground
                lightEffectTexture.SetValue(grassTexture);

            // 3: build cumulative world matrix using I.S.R.O.T. sequence
            // identity, scale, rotate, orbit(translate & rotate), translate
            world = /*scale **/ rotateX * rotateY * translate;

            // 4: pass parameters to shader
            lightEffectWVP.SetValue(world * cam.viewMatrix * cam.projectionMatrix);
            lightEffectWorld.SetValue(world);
            
            
            // attempting to shine the light on both the enemy and the player
            // at the same time, but it apparently doesn't work that way.
            if(surfaceName == "ghost")
                lightEffectPosition.SetValue(new Vector4(ghostPosition.X, (ghostPosition.Y+1.0f), ghostPosition.Z, 1.0f));
            else if (surfaceName == "ground")
                lightEffectPosition.SetValue(new Vector4(cam.position, 1.0f));

            lightEffectIntensity.SetValue(5.0f);
            lightEffectColor.SetValue(new Vector4(1.0f, 1.0f, 1.0f, 1.0f));
            
            

            // 5: draw object - select vertex type, primitive type, index, and draw
            LightingShader(PrimitiveType.TriangleStrip);
        }

        /// <summary>
        /// This method is called when the program begins to set game application
        /// properties such as status bar title and draw mode.  It initializes the  
        /// camera viewer projection, vertex types, and shaders.
        /// </summary>
        private void InitializeBaseCode()
        {
            // set status bar in PC Window (there is none for the Xbox 360)
            Window.Title = "Pac";

            // see both sides of objects drawn
            graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;

            // set camera matrix
            cam.SetProjection(Window.ClientBounds.Width,
                              Window.ClientBounds.Height);

            // initialize vertex types
            positionColor = new VertexDeclaration(graphics.GraphicsDevice,
                                          VertexPositionColor.VertexElements);
            positionColorTexture = new VertexDeclaration(graphics.GraphicsDevice,
                                          VertexPositionColorTexture.VertexElements);

            // load PositionColor.fx and set global params
            positionColorEffect = Content.Load<Effect>("Shaders\\PositionColor");
            positionColorEffectWVP = positionColorEffect.Parameters["wvpMatrix"];

            // load Texture.fx and set global params
            textureEffect = Content.Load<Effect>("Shaders\\Texture");
            textureEffectWVP = textureEffect.Parameters["wvpMatrix"];
            textureEffectImage = textureEffect.Parameters["textureImage"];

            positionNormalTexture = new VertexDeclaration(graphics.GraphicsDevice,
                                        VertexPositionNormalTexture.VertexElements);
        }

        /// <summary>
        /// Set vertices for rectangular surface that is drawn using a triangle strip.
        /// </summary>
        private void InitializeGround()
        {
            const float BORDER = BOUNDARY;
            Vector2 uv = new Vector2(0.0f, 0.0f);
            Vector3 pos = new Vector3(0.0f, 0.0f, 0.0f);
            Color color = Color.White;

            // top left
            uv.X = 0.0f; uv.Y = 0.0f; pos.X = -BORDER; pos.Y = 0.0f; pos.Z = -BORDER;
            groundVertices[0] = new VertexPositionColorTexture(pos, color, uv);

            // bottom left
            uv.X = 0.0f; uv.Y = 10.0f; pos.X = -BORDER; pos.Y = 0.0f; pos.Z = BORDER;
            groundVertices[1] = new VertexPositionColorTexture(pos, color, uv);

            // top right
            uv.X = 10.0f; uv.Y = 0.0f; pos.X = BORDER; pos.Y = 0.0f; pos.Z = -BORDER;
            groundVertices[2] = new VertexPositionColorTexture(pos, color, uv);

            // bottom right
            uv.X = 10.0f; uv.Y = 10.0f; pos.X = BORDER; pos.Y = 0.0f; pos.Z = BORDER;
            groundVertices[3] = new VertexPositionColorTexture(pos, color, uv);
        }

        /// <summary>
        /// Initializes the position for each texture rectangles in the skybox.
        /// </summary>
        private void InitializeSkybox()
        {
            Vector3 pos = Vector3.Zero;
            Vector2 uv = Vector2.Zero;
            Color color = Color.White;

            const float MAX = 0.997f; // offset to remove white seam at image edge
            const float MIN = 0.003f; // offset to remove white seam at image edge

            // set position, image, and color data for each vertex in rectangle

            pos.X = +EDGE; pos.Y = -EDGE; uv.X = MIN; uv.Y = MAX; // Bottom R
            skyVertices[0] = new VertexPositionColorTexture(pos, color, uv);

            pos.X = +EDGE; pos.Y = +EDGE; uv.X = MIN; uv.Y = MIN; // Top R
            skyVertices[1] = new VertexPositionColorTexture(pos, color, uv);

            pos.X = -EDGE; pos.Y = -EDGE; uv.X = MAX; uv.Y = MAX; // Bottom L
            skyVertices[2] = new VertexPositionColorTexture(pos, color, uv);

            pos.X = -EDGE; pos.Y = +EDGE; uv.X = MAX; uv.Y = MIN; // Top L
            skyVertices[3] = new VertexPositionColorTexture(pos, color, uv);
        }

        /// <summary>
        /// Sets the coordinates for the primitive wall that wraps around the map
        /// </summary>
        private void InitializeWallVertices()
        {
            //Vector2 uv = new Vector2(0.0f, 0.0f);
            Color color = Color.White; // colour for the strip, in this case, no colour shading is being applied, hence, Color.White

            // copy BOUNDARY, just so we don't risk modifying it
            float WBOUND = BOUNDARY;

            // this is used to scale the UV map to any size BOUNDARY
            float UBOUND = BOUNDARY;

            // coordinates for UV texture mapping
            Vector2 topLeft = new Vector2(0.0f, 0.0f);
            Vector2 topRight = new Vector2(UBOUND + 0.1f, 0.0f);
            Vector2 bottomLeft = new Vector2(0.0f, 1.0f);
            Vector2 bottomRight = new Vector2(UBOUND + 0.1f, 1.0f);

            // front wall
            wallVertices[0] =
                new VertexPositionColorTexture(new Vector3(-WBOUND, 1.6f, WBOUND), color, topLeft);      // 
            wallVertices[1] =
                new VertexPositionColorTexture(new Vector3(-WBOUND, 0.0f, WBOUND), color, bottomLeft);   // 
            wallVertices[2] =
                new VertexPositionColorTexture(new Vector3(WBOUND, 1.6f, WBOUND), color, topRight);      // 
            wallVertices[3] =
                new VertexPositionColorTexture(new Vector3(WBOUND, 0.0f, WBOUND), color, bottomRight);   // 

            // right wall
            wallVertices[4] =
                new VertexPositionColorTexture(new Vector3(WBOUND, 0.0f, WBOUND), color, bottomLeft);    // l
            wallVertices[5] =
                new VertexPositionColorTexture(new Vector3(WBOUND, 1.6f, WBOUND), color, topLeft);       // 
            wallVertices[6] =
                new VertexPositionColorTexture(new Vector3(WBOUND, 0.0f, -WBOUND), color, bottomRight);  // 
            wallVertices[7] =
                new VertexPositionColorTexture(new Vector3(WBOUND, 1.6f, -WBOUND), color, topRight);     // 

            // back wall
            wallVertices[8] =
                new VertexPositionColorTexture(new Vector3(WBOUND, 1.6f, -WBOUND), color, topLeft);      // 
            wallVertices[9] =
                new VertexPositionColorTexture(new Vector3(WBOUND, 0.0f, -WBOUND), color, bottomLeft);   // 
            wallVertices[10] =
                new VertexPositionColorTexture(new Vector3(-WBOUND, 1.6f, -WBOUND), color, topRight);   // 
            wallVertices[11] =
                new VertexPositionColorTexture(new Vector3(-WBOUND, 0.0f, -WBOUND), color, bottomRight);//

            // left wall
            wallVertices[12] =
                new VertexPositionColorTexture(new Vector3(-WBOUND, 0.0f, -WBOUND), color, bottomLeft); // 
            wallVertices[13] =
                new VertexPositionColorTexture(new Vector3(-WBOUND, 1.6f, -WBOUND), color, topLeft);    // 
            wallVertices[14] =
                new VertexPositionColorTexture(new Vector3(-WBOUND, 0.0f, WBOUND), color, bottomRight); // 
            wallVertices[15] =
                new VertexPositionColorTexture(new Vector3(-WBOUND, 1.6f, WBOUND), color, topRight);    // 
        }

        

        /// <summary>
        /// Initializes the primitive for the ghost using 
        /// </summary>
        private void InitializeGhost()
        {
            // the AI is initialized as OFF
            // AI ON means that the enemy will be chasing the player
            ghost.SetAI(OFF);

            // coordinates for UV texture mapping
            Vector2 topLeft = new Vector2(0.0f, 0.0f);
            Vector2 topRight = new Vector2(1.0f, 0.0f);
            Vector2 bottomLeft = new Vector2(0.0f, 1.0f);
            Vector2 bottomRight = new Vector2(1.0f, 1.0f);

            // values for the position and color of the primitives
            // this just makes the code easier to read
            Vector3 position;
            Color color = Color.White;

            // the actual coordinates are as follows;
            position = new Vector3(-0.5f, 1.0f, 0.0f);  // top left
            ghostPrimitive[0] = new VertexPositionColorTexture(position, color, topLeft);

            position = new Vector3(-0.5f, 0.0f, 0.0f);  // bottom left
            ghostPrimitive[1] = new VertexPositionColorTexture(position, color, bottomLeft);

            position = new Vector3(0.5f, 1.0f, 0.5f); // top right
            ghostPrimitive[2] = new VertexPositionColorTexture(position, color, topRight);

            position = new Vector3(0.5f, 0.0f, 0.5f); // bottom right
            ghostPrimitive[3] = new VertexPositionColorTexture(position, color, bottomRight);
        }

        /// <summary>
        /// Executes set-up routines when program begins. 
        /// </summary>
        protected override void Initialize()
        {
            InitializeBaseCode();
            InitializeGround();
            InitializeIndices();
            InitializeVertexBuffer();
            InitializeBasicEffect();

            // initialize vertices for the skybox
            InitializeSkybox();

            // initialize vertices for the walls
            InitializeWallVertices();
            InitializeWallCollision();
            
            // initializes the collision sphere for the player
            InitializePlayerSphere();

            // ... as well as for pinky
            InitializeGhostSphere();

            // ... and the shotgun
            InitializeGunSphere();

            lightEffect = Content.Load<Effect>("Shaders\\PointLightPS");
            lightEffectWVP = lightEffect.Parameters["wvpMatrix"];
            lightEffectWorld = lightEffect.Parameters["worldMatrix"];
            lightEffectPosition = lightEffect.Parameters["lightPosition"];
            lightEffectIntensity = lightEffect.Parameters["lightIntensity"];
            lightEffectTexture = lightEffect.Parameters["textureImage"];
            lightEffectColor = lightEffect.Parameters["color"];

            // make sure to comment these out if you have them initialized in UpdateGhostPosition()
            // when the random numbers are generated here, the speed will be different each
            // time the game is run, and will be constant through the entire game
            ///////////////////////////////////////////////////////////////////////
            // NOTE: the random speed and direction for the ghost movement has been fixed,
            //       this code will remain here for archiving purposes only.
            speed.X = -1.0f - randomNumber.Next(3);
            speed.Z = -1.0f - randomNumber.Next(3);

            // initialize vertices used by the ghost primitive
            InitializeGhost();

            for (int i = 0; i < NUM_SHELLS; i++)
                shell[i] = new Projectile(BOUNDARY);

            base.Initialize();
        }

        /// <summary>
        /// Draws colored surfaces with PositionColor.fx shader. 
        /// </summary>
        /// <param name="primitiveType">Object type drawn with vertex data.</param>
        /// <param name="vertexData">Array of vertices.</param>
        /// <param name="numPrimitives">Total primitives drawn.</param>
        private void PositionColorShader(PrimitiveType primitiveType,
                                         VertexPositionColor[] vertexData,
                                         int numPrimitives)
        {
            positionColorEffect.Begin(); // begin using PositionColor.fx
            positionColorEffect.Techniques[0].Passes[0].Begin();

            // set drawing format and vertex data then draw primitive surface
            graphics.GraphicsDevice.VertexDeclaration = positionColor;
            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                                    primitiveType, vertexData, 0, numPrimitives);

            positionColorEffect.Techniques[0].Passes[0].End();
            positionColorEffect.End();  // stop using PositionColor.fx
        }

        /// <summary>
        /// Draws textured primitive objects using Texture.fx shader. 
        /// </summary>
        /// <param name="primitiveType">Object type drawn with vertex data.</param>
        /// <param name="vertexData">Array of vertices.</param>
        /// <param name="numPrimitives">Total primitives drawn.</param>
        private void TextureShader(PrimitiveType primitiveType,
                                   VertexPositionColorTexture[] vertexData,
                                   int numPrimitives)
        {
            textureEffect.Begin(); // begin using Texture.fx
            textureEffect.Techniques[0].Passes[0].Begin();

            // set drawing format and vertex data then draw surface
            graphics.GraphicsDevice.VertexDeclaration = positionColorTexture;
            graphics.GraphicsDevice.DrawUserPrimitives
                                    <VertexPositionColorTexture>(
                                    primitiveType, vertexData, 0, numPrimitives);

            textureEffect.Techniques[0].Passes[0].End();
            textureEffect.End(); // stop using Textured.fx
        }

        /// <summary>
        /// Triggers drawing of ground with texture shader.
        /// </summary>
        private void DrawGround()
        {
            // 1: declare matrices
            Matrix world, translation;

            // 2: initialize matrices
            translation = Matrix.CreateTranslation(0.0f, 0.0f, 0.0f);

            // 3: build cumulative world matrix using I.S.R.O.T. sequence
            // identity, scale, rotate, orbit(translate & rotate), translate
            world = translation;

            // 4: set shader parameters
            textureEffectWVP.SetValue(world * cam.viewMatrix * cam.projectionMatrix);
            textureEffectImage.SetValue(groundTexture);

            // 5: draw object - primitive type, vertex data, # primitives
            TextureShader(PrimitiveType.TriangleStrip, groundVertices, 2);
        }

        /// <summary>
        /// Draws the rectangles for the skybox with the previously initialized vertices
        /// </summary>
        private void DrawSkybox()
        {
            const float DROP = -1.2f;

            // 1: declare matrices and set defaults
            Matrix world;
            Matrix rotationY = Matrix.CreateRotationY(0.0f);
            Matrix rotationX = Matrix.CreateRotationX(0.0f);
            Matrix translation = Matrix.CreateTranslation(0.0f, 0.0f, 0.0f);
            Matrix camTranslation // move skybox with camera
                                = Matrix.CreateTranslation(
                                  cam.position.X, 0.0f, cam.position.Z);

            // 2: set transformations and also texture for each wall
            for (int i = 0; i < 5; i++)
            {
                switch (i)
                {
                    case 0: // BACK
                        translation = Matrix.CreateTranslation(0.0f, DROP, EDGE);
                        textureEffectImage.SetValue(backTexture);
                        break;
                    case 1: // RIGHT
                        translation = Matrix.CreateTranslation(-EDGE, DROP, 0.0f);
                        rotationY = Matrix.CreateRotationY(-(float)Math.PI / 2.0f);
                        textureEffectImage.SetValue(rightTexture);
                        break;
                    case 2: // FRONT
                        translation = Matrix.CreateTranslation(0.0f, DROP, -EDGE);
                        rotationY = Matrix.CreateRotationY((float)Math.PI);
                        textureEffectImage.SetValue(frontTexture);
                        break;
                    case 3: // LEFT
                        translation = Matrix.CreateTranslation(EDGE, DROP, 0.0f);
                        rotationY = Matrix.CreateRotationY((float)Math.PI / 2.0f);
                        textureEffectImage.SetValue(leftTexture);
                        break;
                    case 4: // SKY
                        translation = Matrix.CreateTranslation(0.0f, EDGE + DROP, 0.0f);
                        rotationX = Matrix.CreateRotationX(-(float)Math.PI / 2.0f);
                        rotationY =
                        Matrix.CreateRotationY(3.0f * MathHelper.Pi / 2.0f);
                        textureEffectImage.SetValue(skyTexture);
                        break;
                }
                // 3: build cumulative world matrix using I.S.R.O.T. sequence
                world = rotationX * rotationY * translation * camTranslation;

                // 4: set shader variables
                textureEffectWVP.SetValue(world * cam.viewMatrix
                                                * cam.projectionMatrix);

                // 5: draw object - primitive type, vertices, # primitives
                TextureShader(PrimitiveType.TriangleStrip, skyVertices, 2);
            }
        }

        /// <summary>
        /// Draw primitives for the walls based on vertices initialized previously.
        /// </summary>
        private void DrawWalls()
        {
            // 1: declare matrices
            Matrix world, translation;

            // 2: initialize matrices
            translation = Matrix.CreateTranslation(0.0f, 0.0f, 0.0f);

            // 3: build cumulative world matrix using I.S.R.O.T. sequence
            // identity, scale, rotate, orbit(translate & rotate), translate
            world = translation;

            // 4: set the shader parameters
            textureEffectWVP.SetValue(world * cam.viewMatrix * cam.projectionMatrix);
            textureEffectImage.SetValue(wallTexture);

            // 5: draw object - set primitive type, vertex data, # of primitives

            // draw the vertices in triangleStrip for the walls
            TextureShader(PrimitiveType.TriangleStrip, wallVertices, 14);
        }

        /// <summary>
        /// Draws the primitive for the ghost based on vertices set in InitializeGhost()
        /// </summary>
        private void DrawGhost()
        {
            // 1: declare matrices
            Matrix world, translation, rotationY;

            // 2: initialize matrices
            translation = Matrix.CreateTranslation(ghostPosition);
            rotationY = Matrix.CreateRotationY(GetViewerAngle());

            textureEffectImage.SetValue(ghostTexture);

            // 3: build cumulative world matrix using I.S.R.O.T. sequence
            // identity, scale, rotate, orbit(translate & rotate), translate
            world = rotationY * translation;

            // 4: set shader parameters
            textureEffectWVP.SetValue(world * cam.viewMatrix * cam.projectionMatrix);

            // 5: draw object - primitive type, vertices, total primitives
            TextureShader(PrimitiveType.TriangleStrip, ghostPrimitive, 2);
        }
		
        /// <summary>
        /// Draws the maze model that was loaded in LoadContent()
        /// </summary>
        /// <param name="model">The model for the maze</param>
        /// <param name="modelNum">I think this is unused</param>
        /// <param name="gameTime">Game time</param>
		void DrawMaze(Model model, int modelNum, GameTime gameTime)
        {
            //graphics.GraphicsDevice.RenderState.CullMode  //dont draw backface
            //              = CullMode.CullClockwiseFace;   //when many vertices

            foreach (ModelMesh mesh in model.Meshes)
            {
                //1: declare matrices
                Matrix world, scale, rotationZ, translation;

                //2: initialize matrices
                scale       = Matrix.CreateScale(0.03f, 0.03f, 0.03f);
                translation = Matrix.CreateTranslation(0.0f, 0.05f, -4.0f);
                rotationZ   = Matrix.CreateRotationZ(0.0f);

				// PRETTY SUIRE WE DONT NEED THIS
                //if (modelNum == MODEL_NUM)
                //{
                    //calculate time between frames for system independent speed
                    //fanRotation += gameTime.ElapsedRealTime.Ticks / 6000000.0f;
                    //prevent var overflow - store remainder
                    //fanRotation = fanRotation % (2.0f * (float)Math.PI);
                    //rotationZ   = Matrix.CreateRotationZ(fanRotation);
               // }

                //3: build cumulative world matrix using I.S.R.O.T. sequence
                //identity, scale, rotate, orbit, translate
                world = scale * rotationZ * translation;

                //4: set shader parameters
                foreach (BasicEffect effect in mesh.Effects)
                {
                    if (modelNum == MODEL_NUM)
                        effect.World = mazeMatrix[mesh.ParentBone.Index] * world;

                    effect.View         = cam.viewMatrix;
                    effect.Projection   = cam.projectionMatrix;
                    effect.EnableDefaultLighting();
                }
                //5: draw object
                mesh.Draw();
            }
            //stop culling
            graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
        }

        /// <summary>
        /// Draws the model for the shotgun.
        /// </summary>
        /// <param name="model">The model to be drawn.</param>
        /// <param name="gameTime">Timing variable.</param>
        void DrawGun(Model model, GameTime gameTime)
        {
            //graphics.GraphicsDevice.RenderState.CullMode  //dont draw backface
            //              = CullMode.CullClockwiseFace;   //when many vertices

            foreach (ModelMesh mesh in model.Meshes)
            {
                //1: declare matrices
                Matrix world, scale, rotationX, rotationY, translation;

                rotationX = Matrix.CreateRotationX(0.0f);
                rotationY = Matrix.CreateRotationY(0.0f);
                translation = Matrix.CreateTranslation(0.0f, 0.0f, 0.0f);

                //2: initialize matrices
                scale = Matrix.CreateScale(0.001f, 0.001f, 0.001f);
                world = Matrix.Identity;

                // if the gun has not been picked up, draw it on the ground and rotate it
                if (!GUN_ACTIVE)
                {
                    translation = Matrix.CreateTranslation(5.0f, 0.5f, -4.0f);
                    rotationY = Matrix.CreateRotationY(0.0f);

                    //calculate time between frames for system independent speed
                    gunRotation += gameTime.ElapsedRealTime.Ticks / 60000000.0f;
                    //prevent var overflow - store remainder
                    gunRotation = gunRotation % (2.0f * (float)Math.PI);
                    rotationY = Matrix.CreateRotationY(gunRotation);
                }
                else if (GUN_ACTIVE) // if the gun HAS been picked up, draw it in first person perspective
                {
                    translation = Matrix.CreateTranslation(cam.position.X, BASE_HEIGHT + .07f,
                                                           cam.position.Z);

                    Vector3 look = cam.view - cam.position;
                    rotationX = Matrix.CreateRotationX(-MathHelper.Pi * look.Y);
                    rotationY = Matrix.CreateRotationY((float)Math.Atan2(look.X,
                                                                         look.Z) - (float)Math.PI/2.0f);
                    
                }

                //3: build cumulative world matrix using I.S.R.O.T. sequence
                //identity, scale, rotate, orbit, translate
                if(!GUN_ACTIVE)
                    world = scale * rotationY * translation;
                else if(GUN_ACTIVE)
                    world = scale * rotationX * rotationY * translation;

                //4: set shader parameters
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = gunMatrix[mesh.ParentBone.Index] * world;

                    effect.View = cam.viewMatrix;
                    effect.Projection = cam.projectionMatrix;
                    effect.EnableDefaultLighting();
                }
                //5: draw object
                mesh.Draw();
            }
            //stop culling
            graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
        }

        private void DrawShells(Model model, int i)
        {
            // 1: declare matrices
            Matrix world, scale, rotateX, translate;

            // 2: initialize matrices
            scale = Matrix.CreateScale(0.0033f, 0.0033f, 0.0033f);
            rotateX = Matrix.CreateRotationX(-MathHelper.Pi / 2.0f);
            translate = Matrix.CreateTranslation(shell[i].position);

            // 3: build cumulative matrix using I.S.R.O.T. sequence
            world = scale * rotateX * shell[i].directionMatrix * translate;

            // 4: set shader parameters
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = shellMatrix[mesh.ParentBone.Index]
                                 * world;
                    effect.View = cam.viewMatrix;
                    effect.Projection = cam.projectionMatrix;
                    effect.EnableDefaultLighting();
                    effect.SpecularPower = 16.5f;
                }
                // 5: draw object
                mesh.Draw();
            }
        }


        private void LaunchShell(int i)
        {
            Matrix orbitTranslate, orbitX, orbitY, translate, position;
            Vector3 look, start;

            // create matrix and store origin in first row
            position = new Matrix(); // zero matrix
            position.M14 = 1.0f;         // set W to 1 so you can transform it

            // move to tip of launcher
            orbitTranslate = Matrix.CreateTranslation(-0.375f, 0.0f, -0.85f);

            // use same direction as launcher
            look = cam.view - cam.position;

            // offset needed to rotate shell about X to see it with camera
            float offsetAngle = MathHelper.Pi;

            // adjust angle about X with changes in Look (Forward) direction
            orbitX = Matrix.CreateRotationX(offsetAngle - MathHelper.Pi * look.Y);

            // shell's Y direction is same as camera's at time of launch
            orbitY = Matrix.CreateRotationY((float)Math.Atan2(look.X, look.Z));

            // move shell to camera position where launcher base is also located
            translate = Matrix.CreateTranslation(cam.position.X, BASE_HEIGHT + .07f,
                                                           cam.position.Z);

            // use the I.S.R.O.T. sequence to get shell start position
            position = position * orbitTranslate * orbitX * orbitY * translate;

            // convert from matrix back to vector so it can be used for updates
            start = new Vector3(position.M11, position.M12, position.M13);
            shell[i].Launch(look, start);
        }

        Matrix TransformCamera(Camera camera)
        {
            // it's set to 3.0f right now just so you are able to see the sphere,
            // from game start, look up and to the right.
            // yeah....we need to fix that matrix....
            return Matrix.CreateTranslation(camera.position.X, 3.0f, camera.position.Z);
        }
        
        /// <summary>
        /// Helper method that returns a translation matrix that is used for the ghost's bounding sphere.
        /// It's these types of functions that make me glad I payed attention in Mark's class.
        /// </summary>
        /// <returns>The translation matrix for the ghost (position).</returns>
        Matrix GhostTransformMatrix() { return Matrix.CreateTranslation(ghostPosition); }

        /// <summary>
        /// Draws bounding spheres based on their group.
        /// </summary>
        /// <param name="group">The SphereGroup of the sphere being drawn.</param>
        /// <param name="camera">Camera (player)</param>
        /// <param name="gameTime">Timing variable.</param>
        private void DrawSpheres(int group, Camera camera, GameTime gameTime)
        {
            // matrix for the game world, where to draw spheres
            Matrix world;

            for (int j = 0; j < sphereGroup[group].sphere.Count; j++)
            {
                if (sphereGroup[group].Show)
                {
                    world = Matrix.Identity;

                    // player sphere
                    if (group == (int)Group.player)
                        world = TransformCamera(cam);

                    if (group == (int)Group.pinky)
                        world = GhostTransformMatrix();

                    if (group == (int)Group.shotgun)
                    {
                        // do nothing...for now
                    }

                    // 4: set variables in shader
                    positionColorEffectWVP.SetValue(world * cam.viewMatrix * cam.projectionMatrix);

                    // 5: draw object - primitive type, vertex data, # primitives
                    PositionColorShader(sphereGroup[group].primitiveType,
                                        sphereGroup[group].sphere[j].vertices,
                                        sphereGroup[group].PrimitiveCount);
                }
            }
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // create SpriteBatch object for drawing animated 2D images
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // load texture
            grassTexture = Content.Load<Texture2D>("Images\\grass");

            // load each image for a texture in the skybox,
            // as well as the new texutre for the ground
            frontTexture = Content.Load<Texture2D>("Images\\skybox\\front_dusk");
            backTexture = Content.Load<Texture2D>("Images\\skybox\\back_dusk");
            leftTexture = Content.Load<Texture2D>("Images\\skybox\\right_dusk");  // for some reason, these are reversed on my computer
            rightTexture = Content.Load<Texture2D>("Images\\skybox\\left_dusk");  // if the skybox does not match up, simply reverse these two
            skyTexture = Content.Load<Texture2D>("Images\\skybox\\sky_dusk");
            groundTexture = Content.Load<Texture2D>("Images\\ground_dusk");

            spriteFont = Content.Load<SpriteFont>("StatFont");

            // temporary texture for the walls, courtesy of Lukewarm Media inc.
            wallTexture = Content.Load<Texture2D>("Images\\wallCrop");

            // texture for the ghost
            ghostTexture = Content.Load<Texture2D>("Images\\ghost_A");
			
			// load the model for the maze
			mazeModel     = Content.Load<Model>("Models\\Maze");
            mazeMatrix = new Matrix[mazeModel.Bones.Count];
            mazeModel.CopyAbsoluteBoneTransformsTo(mazeMatrix);

            // load the model for the shotgun
            gunModel = Content.Load<Model>("Models\\shotgun");
            gunMatrix = new Matrix[gunModel.Bones.Count];
            gunModel.CopyAbsoluteBoneTransformsTo(gunMatrix);

            // load the model for the shotgun shell
            shellModel = Content.Load<Model>("Models\\shell");
            shellMatrix = new Matrix[shellModel.Bones.Count];
            shellModel.CopyAbsoluteBoneTransformsTo(shellMatrix);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Updates camera viewer in forwards and backwards direction.
        /// </summary>
        float Move()
        {
            KeyboardState kb = Keyboard.GetState();
            GamePadState gp = GamePad.GetState(PlayerIndex.One);
            float move = 0.0f;
            const float SCALE = 1.50f;

            // gamepad in use
            if (gp.IsConnected)
            {
                // left stick shifted left/right
                if (gp.ThumbSticks.Left.Y != 0.0f)
                    move = (SCALE * gp.ThumbSticks.Left.Y);
            }
            // no gamepad - use UP&DOWN or W&S
            else
            {
#if !XBOX
                if (kb.IsKeyDown(Keys.Up) || kb.IsKeyDown(Keys.W))
                    move = 1.0f;  // Up or W - move ahead
                else if (kb.IsKeyDown(Keys.Down) || kb.IsKeyDown(Keys.S))
                    move = -1.0f; // Down or S - move back
#endif
            }
            return move;
        }

        /// <summary>
        /// Updates camera viewer in sideways direction.
        /// </summary>
        float Strafe()
        {
            KeyboardState kb = Keyboard.GetState();
            GamePadState gp = GamePad.GetState(PlayerIndex.One);

            // using gamepad leftStick shifted left / right for strafe
            if (gp.IsConnected)
            {
                if (gp.ThumbSticks.Left.X != 0.0f)
                    return gp.ThumbSticks.Left.X;
            }
            // using keyboard - strafe with Left&Right or A&D
            else if (kb.IsKeyDown(Keys.Left) || kb.IsKeyDown(Keys.A))
                return -1.0f; // strafe left
            else if (kb.IsKeyDown(Keys.Right) || kb.IsKeyDown(Keys.D))
                return 1.0f;  // strafe right
            return 0.0f;
        }

        /// <summary>
        /// Returns the value of rotation about the Y-axis for the camera.
        /// Used for billboarding.
        /// </summary>
        /// <returns>Rotation about Y-axis</returns>
        float GetViewerAngle()
        {
            // use camera look direction to get
            // rotation angle about Y
            float x = cam.view.X - cam.position.X;
            float z = cam.view.Z - cam.position.Z;
            return (float)Math.Atan2(x, z) + (MathHelper.Pi + MathHelper.Pi / 7);
        }

        /// <summary>
        /// Changes camera viewing angle.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        Vector2 ChangeView(GameTime gameTime)
        {
            const float SENSITIVITY = 250.0f;
            const float VERTICAL_INVERSION = -1.0f;   // vertical view control
            // negate to reverse

            // handle change in view using right and left keys
            KeyboardState kbState = Keyboard.GetState();
            int widthMiddle = Window.ClientBounds.Width / 2;
            int heightMiddle = Window.ClientBounds.Height / 2;
            Vector2 change = Vector2.Zero;
            GamePadState gp = GamePad.GetState(PlayerIndex.One);

            if (gp.IsConnected == true) // gamepad on PC / Xbox
            {
                float scaleY = VERTICAL_INVERSION * (float)
                               gameTime.ElapsedGameTime.Milliseconds / 50.0f;
                change.Y = scaleY * gp.ThumbSticks.Right.Y * SENSITIVITY;
                change.X = gp.ThumbSticks.Right.X * SENSITIVITY;
            }
            else
            {
                // use mouse only (on PC)
#if !XBOX
                float scaleY = VERTICAL_INVERSION * (float)
                               gameTime.ElapsedGameTime.Milliseconds / 100.0f;
                float scaleX = (float)gameTime.ElapsedGameTime.Milliseconds / 400.0f;

                // get cursor position
                mouse = Mouse.GetState();

                // cursor not at center on X
                if (mouse.X != widthMiddle)
                {
                    change.X = mouse.X - widthMiddle;
                    change.X /= scaleX;
                }
                // cursor not at center on Y
                if (mouse.Y != heightMiddle)
                {
                    change.Y = mouse.Y - heightMiddle;
                    change.Y /= scaleY;
                }
                // reset cursor back to center
                Mouse.SetPosition(widthMiddle, heightMiddle);
#endif
            }
            return change;
        }

        Rectangle TitleSafeRegion(string outputString, SpriteFont font)
        {
            Vector2 stringDimensions = font.MeasureString(outputString);
            int stringWidth = (int)stringDimensions.X; // string pixel width
            int stringHeight = (int)stringDimensions.Y; // font pixel height

            // some televisions only show 80% of the window
            const float UNSAFEAREA = 0.2f;
            const float MARGIN = UNSAFEAREA / 2.0f;

            // calculate title safe bounds for string
            int top, left, safeWidth, safeHeight;
            top = (int)(Window.ClientBounds.Height * MARGIN);
            left = (int)(Window.ClientBounds.Width * MARGIN);
            safeWidth = (int)((1.0f - UNSAFEAREA) * Window.ClientBounds.Width)
                                    - stringWidth;
            safeHeight = (int)((1.0f - UNSAFEAREA) * Window.ClientBounds.Height)
                                    - stringHeight;
            return new Rectangle(left, top, safeWidth, safeHeight);
        }

        private void DrawFonts(GameTime gameTime)
        {
            string outputString;
            Rectangle safeArea;

            // start drawing font sprites
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, // enable transparency
                                SpriteSortMode.Immediate,   // use manual order
                                SaveStateMode.SaveState);   // store 3D settings
            
            /*outputString = cam.position.X.ToString();
            safeArea = TitleSafeRegion(outputString, spriteFont);

            spriteBatch.DrawString(spriteFont, outputString, new Vector2(
                                   safeArea.Left, safeArea.Top), Color.Yellow);*/

            DrawFonts();
            // stop drawing - and 3D settings are restored if SaveState used
            spriteBatch.End();
        }

        private void DrawFonts()
        {
            Rectangle drawArea;
            drawArea = TitleSafeRegion("Test String", spriteFont); //start pixel
            int X = (int)drawArea.X;                                //start X
            int Y = (int)drawArea.Y;                                //start Y

            ShowString("Current Position    ", X, Y += 20);
            ShowString("                         X:  " + cam.position.X, X, Y += 20);
            ShowString("                         Z:  " + cam.position.Z, X, Y += 20);
            ShowString("distance from ghost (X, Z):  " + distanceFromGhost.X + ", " + distanceFromGhost.Z, X, Y += 20);
            ShowString("                         Shotgun sphere:  " + sphereGroup[(int)Group.shotgun].sphere[0].boundingSphere.Center.X +
                ", " + sphereGroup[(int)Group.shotgun].sphere[0].boundingSphere.Center.Z, X, Y += 20);
            ShowString("                         Ghost sphere:  " + sphereGroup[(int)Group.pinky].sphere[0].boundingSphere.Center.X +
                ", " + sphereGroup[(int)Group.pinky].sphere[0].boundingSphere.Center.Z, X, Y += 20);

            //ShowString("                 :  " + cam.position.X, X, Y += 20);
        }

        private void ShowString(String output, int X, int Y)
        {
            spriteBatch.DrawString(spriteFont, output, new Vector2((float)X, (float)Y), Color.Red);
        }

        /// <summary>
        /// Updates the ghost's position, allowing it to move, and redirect its course
        /// when it strikes a wall. In other words, there are no bounding boxes...yet.
        /// </summary>
        /// <param name="gameTime">Game time.</param>
        void UpdateGhostPosition(GameTime gameTime)
        {
            distanceFromGhost = ghostPosition - cam.position;

            float distance = Vector3.Distance(ghostPosition, cam.position);

            /*if (distanceFromGhost.X < 0)
                distanceFromGhost.X *= -1;

            if (distanceFromGhost.Z < 0)
                distanceFromGhost.Z *= -1;*/


            // singleton-like check to initialize the ghost in a random direction
            if (GHOST_START == 0)
            {
                X_OR_Z = randomNumber.Next(10);
                GHOST_START = 1;// this ensures that the initialization happens only once
            }

            // if the ghost is in wander mode, move according to destination variable
            if (ghost.QuereyAI() == OFF)
            {
                

                // change corresponding speed if beyond worlds X and Z boundaries 
                if (ghostPosition.X > BOUNDARY || ghostPosition.X < -BOUNDARY || 
                    wallBoxes[0].Contains(ghostPosition) == ContainmentType.Contains ||
                    wallBoxes[1].Contains(ghostPosition) == ContainmentType.Contains)
                {
                    speed.X *= -1.0f;
                    //X_OR_Z *= -1;
                }
                if (ghostPosition.Z > BOUNDARY || ghostPosition.Z < -BOUNDARY ||
                    wallBoxes[2].Contains(ghostPosition) == ContainmentType.Contains ||
                    wallBoxes[3].Contains(ghostPosition) == ContainmentType.Contains)
                {
                    speed.Z *= -1.0f;
                    //X_OR_Z *= -1;
                }

                float timeScale = gameTime.ElapsedGameTime.Milliseconds / 1000.0f;

                if (X_OR_Z > 5)
                {
                    ghostPosition.X += speed.X * timeScale;
                }
                else if (X_OR_Z < 5)
                {
                    ghostPosition.Z += speed.Z * timeScale;
                }
                else
                {
                    X_OR_Z = randomNumber.Next(10);
                }

                // I've implemented a simple algorithm that allows the ghost to move
                // in a random direction every few seconds. The psuedocode for this
                // algorithm is as follows;
                /*******************************************************************
                    global gameTime
                    global lastPass = 0
                    global descision = 10 // this is the amount of time the monster will follow a single path

                    function updateEnemy()
                    {
                        gameTime = elapsed.seconds
    	
                        if( (gameTime - lastPass) >= descision )
                            direction = random.next
                    }
                *******************************************************************/

                

                currentTime = gameTime.ElapsedGameTime.Milliseconds;

                if (gameTimeSupp == 100)
                {
                    X_OR_Z = randomNumber.Next(10);

                    gameTimeSupp = 0;
                }
                else gameTimeSupp++;

                if ((currentTime - lastPass) >= descision)
                {
                    X_OR_Z = randomNumber.Next(10);
                    lastPass = currentTime;
                }

                if (distance < 2.0f)//distanceFromGhost.X < 1.0f && distanceFromGhost.Z < 1.0f)
                {
                    ghost.SetAI(true);
                }

            }
            else if (ghost.QuereyAI() == ON)
            {
                float timeScale = gameTime.ElapsedGameTime.Milliseconds / 1000.0f;

                ghostPosition.X -= distanceFromGhost.X * timeScale;// * .003f;
                ghostPosition.Z -= distanceFromGhost.Z * timeScale;// * .003f;


                /*if (distanceFromGhost.X < 0)
                {
                    //distanceFromGhost.Normalize();
                    ghostPosition.X -= distanceFromGhost.X * .003f;
                }
                else
                {
                    ghostPosition.X += distanceFromGhost.X * .003f;
                }

                if (distanceFromGhost.Z < 0)
                {
                    //distanceFromGhost.Normalize();
                    ghostPosition.Z -= distanceFromGhost.Z * .003f;
                }
                else
                {
                    ghostPosition.X += distanceFromGhost.Z * .003f;
                }*/

               
            }

            wallArray.CheckWallCollisions(ghostPosition, speed);

            //for (int i = 0; i < NUM_SHELLS; i++)
            //{/
                BoundingSphere tempSphere = sphereGroup[(int)Group.pinky].sphere[0].boundingSphere;
                BoundingSphere sphereBlah = sphereGroup[(int)Group.shotgun].sphere[0].boundingSphere;

                if (Collision(tempSphere, sphereBlah) == true)
                {
                    GUN_ACTIVE = !GUN_ACTIVE;
                }
           // }

            
        }

        bool Collision(BoundingSphere A, BoundingSphere B)
        {
            if (A.Intersects(B))
            {
                return true;
            }

            return false;
        }

        int getRandomOne()
        {
            int r = randomNumber.Next(-1, 1);
            
            //if you want to only allow -1 and 1 and not 0, you would 
            //recursively call this function until you got either -1 or 1
            
            //if (0 == r)
            //    r = getRandomOne();
            
            return r;
        }

        public void movePlayer()
        {
            // move player only if no collision with walls.  If wall collision occurs, 
            // move player back inside the bounds of the wall.
            if (wallBoxes[0].Contains(cam.position) != ContainmentType.Contains &&
                wallBoxes[1].Contains(cam.position) != ContainmentType.Contains &&
                wallBoxes[2].Contains(cam.position) != ContainmentType.Contains &&
                wallBoxes[3].Contains(cam.position) != ContainmentType.Contains)
            {
                cam.Move(Move());
                cam.Strafe(Strafe());
            }
            else if (wallBoxes[0].Contains(cam.position) == ContainmentType.Contains) // right wall
            {
                cam.position.X -= .02f;
                cam.view.X     -= .02f;
            }
            else if (wallBoxes[1].Contains(cam.position) == ContainmentType.Contains) // left wall
            {
                cam.position.X += .02f;
                cam.view.X     += .02f;
            }
            else if (wallBoxes[2].Contains(cam.position) == ContainmentType.Contains) // back wall
            {
                cam.position.Z -= .02f;
                cam.view.Z     -= .02f;
            }
            else if (wallBoxes[3].Contains(cam.position) == ContainmentType.Contains) // front wall
            {
                cam.position.Z += .02f;
                cam.view.Z     += .02f;
            }

            wallArray.CheckWallCollisions(cam);
        }

        

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            KeyboardState kbState = Keyboard.GetState();
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed
            || kbState.IsKeyDown(Keys.Escape))
            {
                this.Exit();
            }

            if (kbState.IsKeyDown(Keys.LeftShift))
            {
                cam.position.X -= 0.1f;
                cam.view.X -= 0.1f;
            }
            
            if (kbState.IsKeyDown(Keys.F1))
            {
                ghost.SetAI(OFF); // for debugging purposes, turn off ai when f1 key is pressed.
                ghostPosition.X = 5.0f;
                ghostPosition.Z = -3.0f;
            }

            // DEBUG: toggle the state of the gun
            if (kbState.IsKeyDown(Keys.F12))
                GUN_ACTIVE = !GUN_ACTIVE;

 
            // update camera
            cam.SetFrameInterval(gameTime);
            movePlayer();
            cam.SetView(ChangeView(gameTime));

            // refresh key and button states
#if !XBOX
            mouseCurrent = Mouse.GetState();
#endif

            gamepad = GamePad.GetState(PlayerIndex.One);

            // launch rocket for right trigger and left click events
            if (gamepad.Triggers.Right > 0 && gamepadPrevious.Triggers.Right == 0
#if !XBOX
 || mouseCurrent.LeftButton == ButtonState.Pressed
                && mousePrevious.LeftButton == ButtonState.Released
#endif
)
            {
                // if launch event then launch next available rocket
                for (int i = 0; i < NUM_SHELLS; i++)
                    if (shell[i].active == false)
                    {
                        LaunchShell(i);
                        break;
                    }
            }
            // archive current state for comparison next frame
            gamepadPrevious = gamepad;
#if !XBOX
            mousePrevious = mouseCurrent;
#endif
            // update rockets that are in flight
            for (int i = 0; i < NUM_SHELLS; i++)
                if (shell[i].active)
                    shell[i].UpdateProjectile(gameTime);

            // make the ghost move
            UpdateGhostPosition(gameTime);

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // draw the ground based on vertices initialized in InitializeGround()
            //DrawGround();
            DrawIndexedGrid("ground");
            DrawIndexedGrid("ghost");

            // draw each side of the skybox
            DrawSkybox();

            // draw the maze!
            // this MUST be called before DrawGhost(), otherwise the sprite
            // will cull out some of the maze, very unappealing
            DrawMaze(mazeModel, MODEL_NUM, gameTime);

            // draw the collision spheres
            for (int i = 0; i < TOTAL_GROUPS; i++)
                DrawSpheres(i, cam, gameTime);

            // draw the shotgun, same routine as with the maze
            //if(false == GUN_ACTIVE)

            //draw gun whether it is active or not.  If it is not active, it will be drawn
            // on the ground.  If it is active, it will be drawn in first person perspective
                DrawGun(gunModel, gameTime);

            //draw shells from the shotgun
                for (int i = 0; i < NUM_SHELLS; i++)
                    if (shell[i].active)
                        DrawShells(shellModel, i);

            // draw primitives (with alpha)
            graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
            graphics.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
            ///graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;

            // draw the walls
            DrawWalls();

            // draw the ghost
            DrawGhost();

            // turn off alpha
            graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;

            // draw fonts to the game window
            DrawFonts(gameTime);
			            
            base.Draw(gameTime);
        }
    }

}